﻿using System;
using System.Collections.Generic;
using System.IO;
using EnvDTE;
using EnvDTE80;
using VSLangProj;

namespace Neovolve.Extensibility.VisualStudio.ReflectorLink
{
    /// <summary>
    /// The <see cref="LaunchActionLoader"/>
    /// class is used to load a <see cref="LaunchActionCollection"/> object.
    /// </summary>
    internal static class LaunchActionLoader
    {
        /// <summary>
        /// Determines whether the specified application object has active project actions.
        /// </summary>
        /// <param name="applicationObject">The application object.</param>
        /// <returns>
        /// 	<c>true</c> if the specified application object has active project actions; otherwise, <c>false</c>.
        /// </returns>
        public static Boolean HasActiveProjectActions(DTE2 applicationObject)
        {
            LaunchActionCollection actions = LoadActiveProjectActionsInternal(applicationObject, true);

            return EvaluateHasActions(actions);
        }

        /// <summary>
        /// Determines whether the specified application object has selected projects actions.
        /// </summary>
        /// <param name="applicationObject">The application object.</param>
        /// <returns>
        /// 	<c>true</c> if the specified application object has selected projects actions; otherwise, <c>false</c>.
        /// </returns>
        public static Boolean HasSelectedProjectsActions(DTE2 applicationObject)
        {
            LaunchActionCollection actions = LoadSelectedProjectsActionsInternal(applicationObject, true);

            return EvaluateHasActions(actions);
        }

        /// <summary>
        /// Determines whether the specified application object has selected reference items actions.
        /// </summary>
        /// <param name="applicationObject">The application object.</param>
        /// <returns>
        /// 	<c>true</c> if the specified application object has selected reference items actions; otherwise, <c>false</c>.
        /// </returns>
        public static Boolean HasSelectedReferenceItemsActions(DTE2 applicationObject)
        {
            LaunchActionCollection actions = LoadSelectedReferenceItemsActionsInternal(applicationObject, true);

            return EvaluateHasActions(actions);
        }

        /// <summary>
        /// Determines whether the specified application object has selected references actions.
        /// </summary>
        /// <param name="applicationObject">The application object.</param>
        /// <returns>
        /// 	<c>true</c> if the specified application object has selected references actions; otherwise, <c>false</c>.
        /// </returns>
        public static Boolean HasSelectedReferencesActions(DTE2 applicationObject)
        {
            LaunchActionCollection actions = LoadSelectedReferencesActionsInternal(applicationObject, true);

            return EvaluateHasActions(actions);
        }

        /// <summary>
        /// Determines whether the specified application object has selected solution items actions.
        /// </summary>
        /// <param name="applicationObject">The application object.</param>
        /// <returns>
        /// 	<c>true</c> if the specified application object has selected solution items actions; otherwise, <c>false</c>.
        /// </returns>
        public static Boolean HasSelectedSolutionItemsActions(DTE2 applicationObject)
        {
            LaunchActionCollection actions = LoadSelectedSolutionItemsActionsInternal(applicationObject, true);

            return EvaluateHasActions(actions);
        }

        /// <summary>
        /// Determines whether the specified application object has solution actions.
        /// </summary>
        /// <param name="applicationObject">The application object.</param>
        /// <returns>
        /// 	<c>true</c> if the specified application object has solution actions; otherwise, <c>false</c>.
        /// </returns>
        public static Boolean HasSolutionActions(DTE2 applicationObject)
        {
            // Given the logic of LoadSolutionActions, a default action is used if no specific actions are identified
            // This means that there is always an action
            return true;
        }

        /// <summary>
        /// Loads the active project actions.
        /// </summary>
        /// <param name="applicationObject">The application object.</param>
        /// <returns></returns>
        public static LaunchActionCollection LoadActiveProjectActions(DTE2 applicationObject)
        {
            return LoadActiveProjectActionsInternal(applicationObject, false);
        }

        /// <summary>
        /// Loads the selected projects actions.
        /// </summary>
        /// <param name="applicationObject">The application object.</param>
        /// <returns></returns>
        public static LaunchActionCollection LoadSelectedProjectsActions(DTE2 applicationObject)
        {
            return LoadSelectedProjectsActionsInternal(applicationObject, false);
        }

        /// <summary>
        /// Loads the selected reference items actions.
        /// </summary>
        /// <param name="applicationObject">The application object.</param>
        /// <returns></returns>
        public static LaunchActionCollection LoadSelectedReferenceItemsActions(DTE2 applicationObject)
        {
            return LoadSelectedReferenceItemsActionsInternal(applicationObject, false);
        }

        /// <summary>
        /// Loads the selected references actions.
        /// </summary>
        /// <param name="applicationObject">The application object.</param>
        /// <returns></returns>
        public static LaunchActionCollection LoadSelectedReferencesActions(DTE2 applicationObject)
        {
            return LoadSelectedReferencesActionsInternal(applicationObject, false);
        }

        /// <summary>
        /// Loads the selected solution items actions.
        /// </summary>
        /// <param name="applicationObject">The application object.</param>
        /// <returns></returns>
        public static LaunchActionCollection LoadSelectedSolutionItemsActions(DTE2 applicationObject)
        {
            return LoadSelectedSolutionItemsActionsInternal(applicationObject, false);
        }

        /// <summary>
        /// Loads the solution actions.
        /// </summary>
        /// <param name="applicationObject">The application object.</param>
        /// <returns></returns>
        public static LaunchActionCollection LoadSolutionActions(DTE2 applicationObject)
        {
            LaunchActionCollection actions = new LaunchActionCollection();

            // Check if there is a solution loaded
            if (applicationObject.Solution != null)
            {
                foreach (Project proj in applicationObject.Solution.Projects)
                {
                    VSProject vsProj = proj.Object as VSProject;

                    actions.AddRange(PopulateProjectActions(vsProj, false));
                }
            }

            // Check if there were any actions found
            if (actions.Count == 0)
            {
                // This command typically is invoked either from the Tools menu or the context menu of a solution in Solution Explorer
                // If no solution is loaded, it has (outside of keyboard mappings) been invoked from the Tools menu.
                // In this case, if there is no solution open, therefore no actions stored, create a default action to ensure
                // that Reflector is launched. This is because the intention of the user clicking the Tools menu item is to launch
                // Reflector regardless of any references being available in a solution.

                // Add the default launch action
                actions.Add(LaunchAction.Default);
            }

            return actions;
        }

        /// <summary>
        /// Evaluates the has actions.
        /// </summary>
        /// <param name="actions">The actions.</param>
        /// <returns></returns>
        private static bool EvaluateHasActions(ICollection<LaunchAction> actions)
        {
            return (actions != null && actions.Count > 0);
        }

        /// <summary>
        /// Gets the project hierarchy item.
        /// </summary>
        /// <param name="parentItem">The parent item.</param>
        /// <param name="item">The item.</param>
        /// <returns></returns>
        private static UIHierarchyItem GetProjectHierarchyItem(UIHierarchyItem parentItem, UIHierarchyItem item)
        {
            UIHierarchyItems items = parentItem.UIHierarchyItems;

            // Loop through each item
            foreach (UIHierarchyItem checkItem in items)
            {
                if (checkItem == item)
                {
                    return parentItem;
                }

                UIHierarchyItem itemFound = GetProjectHierarchyItem(checkItem, item);

                if (itemFound != null)
                {
                    return itemFound;
                }
            }

            return null;
        }

        /// <summary>
        /// Loads the active project actions internal.
        /// </summary>
        /// <param name="applicationObject">The application object.</param>
        /// <param name="exitAfterFirstFind">if set to <c>true</c> [exit after first find].</param>
        /// <returns></returns>
        private static LaunchActionCollection LoadActiveProjectActionsInternal(
            DTE2 applicationObject, Boolean exitAfterFirstFind)
        {
            // Exit if there is no active document
            if (applicationObject.ActiveDocument == null || applicationObject.ActiveDocument.ProjectItem == null
                || applicationObject.ActiveDocument.ProjectItem.ContainingProject == null)
            {
                return new LaunchActionCollection();
            }

            return PopulateProjectActions(
                applicationObject.ActiveDocument.ProjectItem.ContainingProject, exitAfterFirstFind);
        }

        /// <summary>
        /// Loads the selected projects actions internal.
        /// </summary>
        /// <param name="applicationObject">The application object.</param>
        /// <param name="exitAfterFirstFind">if set to <c>true</c> [exit after first find].</param>
        /// <returns></returns>
        private static LaunchActionCollection LoadSelectedProjectsActionsInternal(
            DTE2 applicationObject, Boolean exitAfterFirstFind)
        {
            // Determine the selected projects
            Object[] selectedItems = applicationObject.ToolWindows.SolutionExplorer.SelectedItems as Object[];
            LaunchActionCollection actions = new LaunchActionCollection();

            if (selectedItems == null)
            {
                return actions;
            }

            // Loop through each selected item
            foreach (Object selectedItem in selectedItems)
            {
                UIHierarchyItem item = selectedItem as UIHierarchyItem;
                LaunchActionCollection projectActions = PopulateProjectActions(item, exitAfterFirstFind);

                actions.AddRange(projectActions);

                // Check if we can exit early
                if (exitAfterFirstFind && actions.Count > 0)
                {
                    break;
                }
            }

            return actions;
        }

        /// <summary>
        /// Loads the selected reference items actions internal.
        /// </summary>
        /// <param name="applicationObject">The application object.</param>
        /// <param name="exitAfterFirstFind">if set to <c>true</c> [exit after first find].</param>
        /// <returns></returns>
        private static LaunchActionCollection LoadSelectedReferenceItemsActionsInternal(
            DTE2 applicationObject, Boolean exitAfterFirstFind)
        {
            Object[] items = applicationObject.ToolWindows.SolutionExplorer.SelectedItems as Object[];
            LaunchActionCollection actions = new LaunchActionCollection();

            // Loop through each item
            if (items == null
                || items.Length == 0)
            {
                return actions;
            }

            // Loop through each item
            foreach (Object item in items)
            {
                UIHierarchyItem hierarchyItem = item as UIHierarchyItem;

                // Check if the item is a UIHierarchyItem
                if (hierarchyItem == null)
                {
                    // This is not an item that we can work with
                    // Move to the next item
                    continue;
                }

                Reference projReference = hierarchyItem.Object as Reference;

                // Check if the reference exists, isn't a project reference and points to a dll file that exists
                if (projReference != null && projReference.SourceProject == null
                    && File.Exists(projReference.Path))
                {
                    LaunchAction action = new LaunchAction();

                    action.ActionType = LaunchActionType.Assembly;
                    action.AssemblyPath = projReference.Path;

                    actions.Add(action);

                    // Check if we can exit early
                    if (exitAfterFirstFind)
                    {
                        break;
                    }
                }
            }

            return actions;
        }

        /// <summary>
        /// Loads the selected references actions internal.
        /// </summary>
        /// <param name="applicationObject">The application object.</param>
        /// <param name="exitAfterFirstFind">if set to <c>true</c> [exit after first find].</param>
        /// <returns></returns>
        private static LaunchActionCollection LoadSelectedReferencesActionsInternal(
            DTE2 applicationObject, Boolean exitAfterFirstFind)
        {
            Object[] selectedItems = applicationObject.ToolWindows.SolutionExplorer.SelectedItems as Object[];
            LaunchActionCollection actions = new LaunchActionCollection();

            // Exit if there are no items
            if (selectedItems == null
                || selectedItems.Length == 0)
            {
                return actions;
            }

            // Loop through each object
            foreach (Object selectedItem in selectedItems)
            {
                UIHierarchyItem findItem = selectedItem as UIHierarchyItem;

                // Skip to the next item if it isn't the correct type
                if (findItem == null)
                {
                    continue;
                }

                // Get the project hierarchy item that contains the selected item
                UIHierarchyItem projectItem = null;

                // Loop through each hierarchy item of the solution
                foreach (UIHierarchyItem treeItem in applicationObject.ToolWindows.SolutionExplorer.UIHierarchyItems)
                {
                    // Attempt to resolve the project for the find item under this tree item
                    projectItem = GetProjectHierarchyItem(treeItem, findItem);

                    // Exit if we have found the related project
                    if (projectItem != null)
                    {
                        break;
                    }
                }

                // Move to the next selected item if there is no project found
                if (projectItem == null)
                {
                    continue;
                }

                LaunchActionCollection projectActions = PopulateProjectActions(projectItem, exitAfterFirstFind);

                // Add the actions for the project
                actions.AddRange(projectActions);

                // Check if we can exit early
                if (exitAfterFirstFind && actions.Count > 0)
                {
                    break;
                }
            }

            return actions;
        }

        /// <summary>
        /// Loads the selected solution items actions internal.
        /// </summary>
        /// <param name="applicationObject">The application object.</param>
        /// <param name="exitAfterFirstFind">if set to <c>true</c> [exit after first find].</param>
        /// <returns></returns>
        private static LaunchActionCollection LoadSelectedSolutionItemsActionsInternal(
            DTE2 applicationObject, Boolean exitAfterFirstFind)
        {
            Object[] items = applicationObject.ToolWindows.SolutionExplorer.SelectedItems as Object[];
            LaunchActionCollection actions = new LaunchActionCollection();

            // Loop through each item
            if (items == null
                || items.Length == 0)
            {
                return actions;
            }

            // Loop through each item
            foreach (Object item in items)
            {
                UIHierarchyItem hierarchyItem = item as UIHierarchyItem;

                // Check if the item is a UIHierarchyItem
                if (hierarchyItem == null)
                {
                    // This is not an item that we can work with
                    // Move to the next item
                    continue;
                }

                ProjectItem projItem = hierarchyItem.Object as ProjectItem;

                if (projItem == null)
                {
                    continue;
                }

                for (Int16 index = 1; index <= projItem.FileCount; index++)
                {
                    String itemPath = projItem.get_FileNames(index);

                    if (File.Exists(itemPath)
                        &&
                        (Path.GetExtension(itemPath).ToLower() == ".exe"
                         || Path.GetExtension(itemPath).ToLower() == ".dll"))
                    {
                        LaunchAction action = new LaunchAction();

                        action.ActionType = LaunchActionType.Assembly;
                        action.AssemblyPath = itemPath;

                        actions.Add(action);

                        // Check if we can exit early
                        if (exitAfterFirstFind)
                        {
                            break;
                        }
                    }
                }

                // Check if we can exit early
                if (exitAfterFirstFind && actions.Count > 0)
                {
                    break;
                }
            }

            return actions;
        }

        /// <summary>
        /// Populates the project actions.
        /// </summary>
        /// <param name="proj">The proj.</param>
        /// <param name="exitAfterFirstFind">if set to <c>true</c> [exit after first find].</param>
        /// <returns></returns>
        private static LaunchActionCollection PopulateProjectActions(Project proj, Boolean exitAfterFirstFind)
        {
            if (proj == null)
            {
                return null;
            }

            // Convert the Project to a VSProject
            VSProject vsProj = proj.Object as VSProject;

            return PopulateProjectActions(vsProj, exitAfterFirstFind);
        }

        /// <summary>
        /// Populates the project actions.
        /// </summary>
        /// <param name="item">The item.</param>
        /// <param name="exitAfterFirstFind">if set to <c>true</c> [exit after first find].</param>
        /// <returns></returns>
        private static LaunchActionCollection PopulateProjectActions(UIHierarchyItem item, Boolean exitAfterFirstFind)
        {
            // Go to the next item if this is not the correct type
            if (item == null)
            {
                return null;
            }

            // Convert the UIHierarchyItem to a Project
            Project proj = item.Object as Project;

            if (proj != null)
            {
                return PopulateProjectActions(proj, exitAfterFirstFind);
            }

            // If the project is contained in a solution folder, the object against the hierarchy item is not a Project
            // For some strange reason, the object is actually a ProjectItem
            ProjectItem projItem = item.Object as ProjectItem;

            if (projItem != null)
            {
                return PopulateProjectActions(projItem.SubProject, exitAfterFirstFind);
            }

            return null;
        }

        /// <summary>
        /// Populates the project actions.
        /// </summary>
        /// <param name="proj">The proj.</param>
        /// <param name="exitAfterFirstFind">if set to <c>true</c> [exit after first find].</param>
        /// <returns></returns>
        private static LaunchActionCollection PopulateProjectActions(VSProject proj, Boolean exitAfterFirstFind)
        {
            // Go to the next item if this is not a project
            if (proj == null)
            {
                return null;
            }

            References refItems = proj.References;

            return PopulateReferencesActions(refItems, exitAfterFirstFind);
        }

        /// <summary>
        /// Populates the references actions.
        /// </summary>
        /// <param name="refItems">The ref items.</param>
        /// <param name="exitAfterFirstFind">if set to <c>true</c> [exit after first find].</param>
        /// <returns></returns>
        private static LaunchActionCollection PopulateReferencesActions(References refItems, Boolean exitAfterFirstFind)
        {
            if (refItems == null)
            {
                return null;
            }

            LaunchActionCollection actions = new LaunchActionCollection();

            // Populuate the references of this project
            foreach (Reference refItem in refItems)
            {
                // Ensure that the reference is valid
                if (!File.Exists(refItem.Path)
                    || refItem.SourceProject != null)
                {
                    continue;
                }

                // Create a launch action for this item
                LaunchAction action = new LaunchAction();

                action.ActionType = LaunchActionType.Assembly;
                action.AssemblyPath = refItem.Path;

                actions.Add(action);

                // Check if we can exit early
                if (exitAfterFirstFind)
                {
                    break;
                }
            }

            return actions;
        }
    }
}